#!/bin/sh
# Copyright 2004  Slackware Linux, Inc., Concord, CA, USA
# Copyright 2004  Patrick J. Volkerding, Concord, CA, USA
# Copyright 2007  Patrick J. Volkerding, Sebeka, MN, USA
# All rights reserved.
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
#  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
#  EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
#  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

MKINITRD_VERSION=1.1.2

print_usage() {
  cat << EOF
Usage: mkinitrd [OPTION]

mkinitrd creates an initial ramdisk (actually an initramfs cpio+gzip
archive) used to load kernel modules that are needed to mount the
root filesystem, or other modules that might be needed before the
root filesystem is available.  Other binaries may be added to the
initrd, and the script is easy to modify.  Be creative.  :-)

  -c      Clear the existing initrd tree first
  -f      Filesystem to use for root partition (must be used with -r)
  --help  Display this message
  -k      Kernel version to use
  -m      A colon (:) delimited list of kernel modules to load.
          Additional options may be added to use when loading the
          kernel modules (but in this case the entire list must be
          wrapped with double quotes).  Any dependencies of requested
          modules will also be added to the initrd.
  -o      Output image (default /boot/initrd.gz)
  -r      Root partition device (must be used with -f)
  -s      Initrd source tree (default /boot/initrd-tree/)
  -C      Use cryptsetup to enable the underlying device of an
          encrypted root partition.  Requires '-r' parameter with the
          *mapped* device name as argument, like:
            -C /dev/sda2 -r cryptroot
          where the actual root device name in /etc/fstab is:
            /dev/mapper/cryptroot
  -L      Add support for LVM partitions
  -V      Display version number

A simple example:  Build an initrd for a reiserfs root partition:

  mkinitrd -c -m reiserfs

Another example:  Build an initrd image using Linux 2.6.20.11-smp kernel
modules for a system with an ext3 root partition on /dev/hdb3:

  mkinitrd -c -k 2.6.20.11-smp -m mbcache:jbd:ext3 -f ext3 -r /dev/hdb3

Note that if you are already logged in with /dev/hdb3 as your /
partition, and it is running ext3, this command works just the same:

  mkinitrd -c -m ext3

If run without options, mkinitrd will rebuild an initrd image using
the contents of the $SOURCE_TREE directory, or, if that directory
does not exist it will be created and populated, and then mkinitrd
will exit.

EOF
}

create_new_source_tree() {
  mkdir -p $SOURCE_TREE
  ( cd $SOURCE_TREE ; tar xzf /usr/share/mkinitrd/initrd-tree.tar.gz )
  # Make sure we have any block devices that might be needed:
  SLOPPY_DEV_LIST=$(cat /proc/partitions)
  for device in $SLOPPY_DEV_LIST ; do
    if [ ! -r $SOURCE_TREE/dev/$device -a -b /dev/$device ]; then
      cp -a /dev/$device $SOURCE_TREE/dev
    fi
  done
  # Make sure a kernel module directory exists:
  mkdir -p $SOURCE_TREE/lib/modules/${KERNEL_VERSION}
}

clear_source_tree() {
  if [ -d "$SOURCE_TREE" ]; then
    rm -rf $SOURCE_TREE
  fi
}

build_initrd_image() {
  # Make sure we have any block devices that might be needed:
  SLOPPY_DEV_LIST=$(cat /proc/partitions)
  for device in $SLOPPY_DEV_LIST ; do
    if [ ! -r $SOURCE_TREE/dev/$device -a -b /dev/$device ]; then
      cp -a /dev/$device $SOURCE_TREE/dev
    fi
  done
  # Wrap the initrd as an initramfs image and move it into place:
  ( cd $SOURCE_TREE
    rm -f $OUTPUT_IMAGE
    find . | cpio -o -H newc | gzip -9c > $OUTPUT_IMAGE
  )
}

# If --help is given, print_usage and exit:
if echo $* | grep -wq '\--help' ; then
  print_usage
  exit 0
fi

# If -V given, print version and exit:
if echo $* | grep -wq '\-V' ; then
  echo "mkinitrd version $MKINITRD_VERSION"
  exit 0
fi

# Default values if these aren't previously set.
# Might be changed by -s and -o options, too.
SOURCE_TREE=${SOURCE_TREE:-/boot/initrd-tree}
OUTPUT_IMAGE=${OUTPUT_IMAGE:-/boot/initrd.gz}
KERNEL_VERSION=${KERNEL_VERSION:-"$(uname -r)"}

# Default actions without options:
if [ -z "$1" ]; then
  # If the output tree doesn't exist, create it and then exit:
  if [ ! -d $SOURCE_TREE ]; then
    echo "Nothing found at location $SOURCE_TREE, so we will create an"
    echo -n "initrd directory structure there... "
    create_new_source_tree
    echo "done."
    echo
    echo "Now cd to $SOURCE_TREE and install some modules in your"
    echo "module directory (lib/modules/${KERNEL_VERSION}).  Then see init"
    echo "for more information (there are a few other files to edit)."
    echo "Finally, run mkinitrd again once the initrd-tree is ready,"
    echo "and $OUTPUT_IMAGE will be created from it."
    echo
    exit 0
  else
    # If the source tree does exist, the default is to build the initrd
    # image from it and then exit:
    build_initrd_image
    echo "$OUTPUT_IMAGE created."
    echo "Be sure to run lilo again if you use it."
    exit 0
  fi
fi # default no-option actions

# Parse options:
while [ ! -z "$1" ]; do
  case $1 in
    -c)
      clear_source_tree
      shift
      ;;
    -f)
      ROOTFS="$2"
      shift 2
      ;;
    -k)
      KERNEL_VERSION="$2"
      shift 2
      ;;
    -m)
      MODULE_LIST="$2"
      shift 2
      ;;
    -o)
      OUTPUT_IMAGE="$2"
      shift 2
      ;;
    -r)
      ROOTDEV="$2"
      shift 2
      ;;
    -s)
      SOURCE_TREE="$2"
      shift 2
      ;;
    -C)
      CRYPT=1
      LUKSDEV="$2"
      shift 2
      ;;
    -L)
      LVM=1
      shift
      ;;
    *) # unknown, prevent infinite loop
      shift
      ;;
  esac
done

# If there's no $SOURCE_TREE, make one now:
if [ ! -d "$SOURCE_TREE" ]; then
  create_new_source_tree
fi

# If $ROOTDEV and $ROOTFS are not set, assume we want the
# values for the currently mounted /:
if [ -z "$ROOTDEV" ]; then
  ROOTDEV=$(mount | grep -w / | cut -f 1 -d ' ')
fi
if [ -z "$ROOTFS" ]; then
  ROOTFS=$(mount | grep -w / | cut -f 5 -d ' ')
fi
# Next, write them in the initrd-tree:
echo $ROOTDEV > $SOURCE_TREE/rootdev
echo $ROOTFS > $SOURCE_TREE/rootfs

# Useful to know which initrd is running:
INITRD_NAME=$(basename $OUTPUT_IMAGE)
echo $INITRD_NAME > $SOURCE_TREE/initrd-name

# Include LVM support in initrd
if [ ! -z "$LVM" ]; then
  if [ -f /sbin/lvm.static ]; then
    mkdir -p $SOURCE_TREE/sbin
    cp /sbin/lvm.static $SOURCE_TREE/sbin/lvm.static
    ( cd $SOURCE_TREE/sbin
      ln -s lvm.static vgchange
      ln -s lvm.static vgscan )
    if ! echo ${MODULE_LIST} | grep -q dm-mod ; then
      MODULE_LIST="$MODULE_LIST:dm-mod"
    fi
  else
    echo "LVM static binary is missing, LVM support isn't installed"
  fi
fi

# Include cryptsetup (LUKS) support in initrd
if [ ! -z "$CRYPT" ]; then
  if [ -e /sbin/cryptsetup.static ]; then
    mkdir -p $SOURCE_TREE/sbin
    cp /sbin/cryptsetup.static $SOURCE_TREE/sbin/cryptsetup.static
    ( cd $SOURCE_TREE/sbin
      ln -s cryptsetup.static cryptsetup
    )
    if ! echo ${MODULE_LIST} | grep -q dm-mod ; then
      MODULE_LIST="$MODULE_LIST:dm-mod"
    fi
    # Write the underlying luks device to the initrd-tree:
    echo $LUKSDEV > $SOURCE_TREE/luksdev
  else
    echo "Cryptsetup static binary is missing, CRYPT support isn't installed"
  fi
fi

# Make module directory:
if [ ! -d $SOURCE_TREE/lib/modules/$KERNEL_VERSION ]; then
  mkdir -p $SOURCE_TREE/lib/modules/$KERNEL_VERSION
fi

# If a module list was given, copy the modules into place:
if [ ! -z "$MODULE_LIST" ]; then
  if grep -q "#insmod /lib/modules/2.6.18.8-smp/reiserfs.ko" $SOURCE_TREE/load_kernel_modules ; then
    rm -f $SOURCE_TREE/load_kernel_modules
    touch $SOURCE_TREE/load_kernel_modules
    chmod 755 $SOURCE_TREE/load_kernel_modules
    echo "# This is a script used to load the kernel modules." >> $SOURCE_TREE/load_kernel_modules
    echo "# To use it, chmod it 755, and then add the insmod" >> $SOURCE_TREE/load_kernel_modules
    echo "# lines needed to load your modules, like this:" >> $SOURCE_TREE/load_kernel_modules
    echo >> $SOURCE_TREE/load_kernel_modules
  fi
  for MODULE in $(echo $MODULE_LIST | tr : \ ); do 
    /sbin/modprobe -q --set-version $KERNEL_VERSION --show-depends $MODULE
  done | cut -f 2 -d ' ' | while read SRCMOD; do 
    if ! grep -q "${SRCMOD}" $SOURCE_TREE/load_kernel_modules 2>/dev/null ; then
      LINE="$(echo "insmod -v $SRCMOD" | sed -e "s/$KERNEL_VERSION/\$(uname -r)/")"
      if ! grep -qx "$LINE" $SOURCE_TREE/load_kernel_modules ; then
        echo "$LINE" >> $SOURCE_TREE/load_kernel_modules
      fi
    fi
    if [ -r "$SRCMOD" ]; then
      cp -a --parents $SRCMOD $SOURCE_TREE
    else
      echo "WARNING:  Could not find module \"$SRCMOD\""
    fi
  done
fi

# And finally, build the initrd:
build_initrd_image

